package com.atom.module.mvvm.extren

import android.annotation.SuppressLint
import android.view.*
import android.widget.AbsListView
import androidx.annotation.CheckResult
import androidx.annotation.IdRes
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import java.lang.RuntimeException

sealed class ViewAttachEvent {
    abstract val view: View

    data class Attached(override val view: View) : ViewAttachEvent()

    data class Detached(override val view: View) : ViewAttachEvent()
}

data class ScrollEvent(
    val view: AbsListView,
    val scrollState: Int,
    val firstVisibleItem: Int,
    val visibleItemCount: Int,
    val totalItemCount: Int
)

data class ScrollChangeEvent(
    val view: View,
    val scrollX: Int,
    val scrollY: Int,
    val oldScrollX: Int,
    val oldScrollY: Int
)

data class LayoutChangeEvent(
    val view: View,
    val left: Int,
    val top: Int,
    val right: Int,
    val bottom: Int,
    val oldLeft: Int,
    val oldTop: Int,
    val oldRight: Int,
    val oldBottom: Int
)

sealed class HierarchyChangeEvent {
    abstract val parent: ViewGroup
    abstract val child: View

    data class ChildAdded(override val parent: ViewGroup, override val child: View) :
        HierarchyChangeEvent()

    data class ChildRemoved(override val parent: ViewGroup, override val child: View) :
        HierarchyChangeEvent()
}

sealed class MenuItemActionViewEvent {
    abstract val menuItem: MenuItem

    data class Collapse(
        override val menuItem: MenuItem
    ) : MenuItemActionViewEvent()

    data class Expand(
        override val menuItem: MenuItem
    ) : MenuItemActionViewEvent()
}

/**
 *
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun View.clicks() = callbackFlow {
    checkMainThread()
    setOnClickListener {
        trySend(it)
    }
    awaitClose { setOnClickListener(null) }
}.conflate()

fun <T : View, R> View.findView(@IdRes id: Int, block: (T) -> R): R {
    val t = this.findViewById<T>(id) ?: throw RuntimeException("can find View")
    return block.invoke(t)
}


@OptIn(ExperimentalCoroutinesApi::class)
fun View.attachEvents() = callbackFlow<ViewAttachEvent> {
    checkMainThread()
    val listener = object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View) {
            trySend(ViewAttachEvent.Attached(v))
        }

        override fun onViewDetachedFromWindow(v: View) {
            trySend(ViewAttachEvent.Detached(v))
        }
    }
    addOnAttachStateChangeListener(listener)
    awaitClose { removeOnAttachStateChangeListener(listener) }
}.conflate()


/**
 *
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun MenuItem.clicks(handled: (MenuItem) -> Boolean = { true }) = callbackFlow<Unit> {
    checkMainThread()
    setOnMenuItemClickListener {
        if (handled(it)) {
            trySend(Unit)
            true
        } else {
            false
        }
    }
    awaitClose { setOnMenuItemClickListener(null) }
}.conflate()


/**
 *
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun MenuItem.actionViewEvents(
    handled: (MenuItemActionViewEvent) -> Boolean = { true }
) = callbackFlow<MenuItemActionViewEvent> {
    checkMainThread()
    val listener = object : MenuItem.OnActionExpandListener {
        override fun onMenuItemActionExpand(item: MenuItem): Boolean {
            val event = MenuItemActionViewEvent.Expand(item)
            return if (handled(event)) {
                trySend(event)
                true
            } else {
                false
            }
        }

        override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
            val event = MenuItemActionViewEvent.Collapse(item)
            return if (handled(event)) {
                trySend(event)
                true
            } else {
                false
            }
        }
    }
    setOnActionExpandListener(listener)
    awaitClose { setOnActionExpandListener(null) }
}.conflate()


/**
 *
 */

@OptIn(ExperimentalCoroutinesApi::class)
fun View.drags(handled: (DragEvent) -> Boolean = { true }) = callbackFlow {
    checkMainThread()
    val listener = View.OnDragListener { _, event ->
        if (handled(event)) {
            trySend(event)
            true
        } else {
            false
        }
    }
    setOnDragListener(listener)
    awaitClose { setOnDragListener(null) }
}.conflate()

/**
 *
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.draws() = callbackFlow {
    checkMainThread()
    val listener = ViewTreeObserver.OnDrawListener {
        trySend(Unit)
    }
    viewTreeObserver.addOnDrawListener(listener)
    awaitClose { viewTreeObserver.removeOnDrawListener(listener) }
}.conflate()

/**
 *
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun View.focusChanges() = callbackFlow {
    checkMainThread()
    val listener = View.OnFocusChangeListener { _, hasFocus ->
        trySend(hasFocus)
    }
    onFocusChangeListener = listener
    awaitClose { onFocusChangeListener = null }
}.conflate().asInitialValueFlow { hasFocus() }


@OptIn(ExperimentalCoroutinesApi::class)
fun View.globalLayouts() = callbackFlow {
    checkMainThread()
    val listener = ViewTreeObserver.OnGlobalLayoutListener {
        trySend(Unit)
    }
    viewTreeObserver.addOnGlobalLayoutListener(listener)
    awaitClose { viewTreeObserver.removeOnGlobalLayoutListener(listener) }
}.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun ViewGroup.hierarchyChangeEvents() =
    callbackFlow<HierarchyChangeEvent> {
        checkMainThread()
        val listener = object : ViewGroup.OnHierarchyChangeListener {
            override fun onChildViewAdded(parent: View, child: View) {
                trySend(
                    HierarchyChangeEvent.ChildAdded(
                        this@hierarchyChangeEvents,
                        child
                    )
                )
            }

            override fun onChildViewRemoved(parent: View, child: View) {
                trySend(
                    HierarchyChangeEvent.ChildRemoved(
                        this@hierarchyChangeEvents,
                        child
                    )
                )
            }
        }
        setOnHierarchyChangeListener(listener)
        awaitClose { setOnHierarchyChangeListener(null) }
    }.conflate()


/**
 *
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.hovers(handled: (MotionEvent) -> Boolean = { true }) =
    callbackFlow {
        checkMainThread()
        val listener = View.OnHoverListener { _, event ->
            if (handled(event)) {
                trySend(event)
                true
            } else {
                false
            }
        }
        setOnHoverListener(listener)
        awaitClose { setOnHoverListener(null) }
    }.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.keys(handled: (KeyEvent) -> Boolean = { true }) = callbackFlow {
    checkMainThread()
    val listener = View.OnKeyListener { _, _, event ->
        if (handled(event)) {
            trySend(event)
            true
        } else {
            false
        }
    }
    setOnKeyListener(listener)
    awaitClose { setOnKeyListener(null) }
}.conflate()


/**
 *
 *
 */
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.layoutChangeEvents() =
    callbackFlow<LayoutChangeEvent> {
        checkMainThread()
        val listener =
            View.OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
                trySend(
                    LayoutChangeEvent(
                        view = v,
                        left = left,
                        top = top,
                        right = right,
                        bottom = bottom,
                        oldLeft = oldLeft,
                        oldTop = oldTop,
                        oldRight = oldRight,
                        oldBottom = oldBottom
                    )
                )
            }
        addOnLayoutChangeListener(listener)
        awaitClose { removeOnLayoutChangeListener(listener) }
    }.conflate()

@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.layoutChanges() = callbackFlow {
    checkMainThread()
    val listener =
        View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            trySend(Unit)
        }
    addOnLayoutChangeListener(listener)
    awaitClose { removeOnLayoutChangeListener(listener) }
}.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.longClicks() = callbackFlow {
    checkMainThread()
    val listener = View.OnLongClickListener {
        trySend(Unit)
        true
    }
    setOnLongClickListener(listener)
    awaitClose { setOnLongClickListener(null) }
}.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.preDraws(proceedDrawingPass: () -> Boolean) = callbackFlow {
    checkMainThread()
    val listener = ViewTreeObserver.OnPreDrawListener {
        trySend(Unit)
        proceedDrawingPass()
    }
    viewTreeObserver.addOnPreDrawListener(listener)
    awaitClose { viewTreeObserver.removeOnPreDrawListener(listener) }
}.conflate()


@SuppressLint("NewApi")
@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.scrollChangeEvents() =
    callbackFlow<ScrollChangeEvent> {
        checkMainThread()
        val listener = View.OnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
            trySend(
                ScrollChangeEvent(
                    view = v,
                    scrollX = scrollX,
                    scrollY = scrollY,
                    oldScrollX = oldScrollX,
                    oldScrollY = oldScrollY
                )
            )
        }
        setOnScrollChangeListener(listener)
        awaitClose { setOnScrollChangeListener(null) }
    }.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.systemUiVisibilityChanges() = callbackFlow {
    checkMainThread()
    val listener = View.OnSystemUiVisibilityChangeListener { flag ->
        trySend(flag)
    }
    setOnSystemUiVisibilityChangeListener(listener)
    awaitClose { setOnSystemUiVisibilityChangeListener(null) }
}.conflate()


@CheckResult
@OptIn(ExperimentalCoroutinesApi::class)
fun View.touches(handled: (MotionEvent) -> Boolean = { true }) = callbackFlow {
    checkMainThread()
    val listener = View.OnTouchListener { _, event ->
        performClick()
        if (handled(event)) {
            trySend(event)
            true
        } else {
            false
        }
    }
    setOnTouchListener(listener)
    awaitClose { setOnTouchListener(null) }
}.conflate()
